pip install scipy
pip install imutils

OpenCV邊緣偵測
1.1 影像前處理
def preprocessing(image):
    # 灰階
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    show_img('gray', gray)
    # 高斯濾波
    gaussian = cv2.GaussianBlur(gray, (9, 9), 0)
    show_img("gaussian", gaussian)
    # 開運算去除白色噪點
    kernel = np.ones((9, 9), np.uint8)
    open = cv2.morphologyEx(gaussian, cv2.MORPH_OPEN, 
                            kernel, iterations=3)
    show_img("open", open)
    return open
1.2 Canny影像邊緣檢測
def edge_detect(image):
    # 以canny邊緣檢測算法獲取目標(50~100為閾值)
    edged = cv2.Canny(image, 50, 100) #低於50刪除 高於100留下
    show_img("edged", edged)
    # 在邊緣圖像中尋找物體輪廓
    contours1, hierarchy = cv2.findContours(edged.copy(), 
                                            cv2.RETR_EXTERNAL,
                                            cv2.CHAIN_APPROX_SIMPLE)
    # 物體輪廓由左到右進行排序
    (contours2, _) = contours.sort_contours(contours1)
    # 若輪廓面積小於100,視為噪音濾除
    contours2 = [i for i in contours2 if cv2.contourArea(i) > 100]
    # 初始化pixelsPerMetric
    pixelsPerMetric = None
    return contours2, pixelsPerMetric
1.3 計算輪廓長度高度,並畫出外切線框
# 計算物體像素尺寸,並轉換為實際尺寸
def measure(image, contours2, pixelsPerMetric):
    origin = image.copy()
    for i in contours2:
        # 計算出物品輪廓之外切線框
        box = cv2.minAreaRect(i)
        box = cv2.cv.BoxPoints(box) if imutils.is_cv2()  \
              else cv2.boxPoints(box)
        box = np.array(box, dtype="int")
        # 左上角座標開始順時針排序,並畫出外切線框
        box = perspective.order_points(box)
        cv2.drawContours(origin, [box.astype("int")], -1, (0, 255, 0), 2)
        # 畫書外切線框端點
        for (x, y) in box:
            cv2.circle(origin, (int(x), int(y)), 5, (0, 0, 255), -1)
        #算出左上和右上端點的中心點、左下和右下端點的中心點
        (tl, tr, br, bl) = box
        (tltrX, tltrY) = midpoint(tl, tr)
        (blbrX, blbrY) = midpoint(bl, br)
        # 算出左上和左下端點的中心點、右上和右下端點的中心點
        (tlblX, tlblY) = midpoint(tl, bl)
        (trbrX, trbrY) = midpoint(tr, br)
        # 計算兩個中心點距離(dA:寬、dB:長)
        dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
        dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))
        # 像素值與最左邊物品實際長度比值
        if pixelsPerMetric is None:
            pixelsPerMetric = dB / image_width
        # 計算目標的實際大小
        dimA = dA / pixelsPerMetric
        dimB = dB / pixelsPerMetric
        # 在圖片中標註物體尺寸
        cv2.putText(origin, "{:.1f}cm".format(dimB),
                    (int(tltrX - 15), int(tltrY - 10)), 
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.65, (255, 255, 255), 2)
        cv2.putText(origin, "{:.1f}cm".format(dimA),
                    (int(trbrX - 10), int(trbrY)),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.65, (255, 255, 255), 2)
    show_img("Image", origin)
    return origin
1.4 完整程式碼
from scipy.spatial import distance as dist
from imutils import perspective
from imutils import contours
import numpy as np
import imutils
import cv2
# 讀取中文路徑圖檔(圖片讀取為BGR)
def cv_imread(image_path):
    image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), -1)
    image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
    return image
# 顯示圖檔
def show_img(name, image):
    cv2.imshow(name, image)
    cv2.waitKey(0)
# 圖片預處理
def preprocessing(image):
    # 灰階
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    show_img('gray', gray)
    # 高斯濾波
    gaussian = cv2.GaussianBlur(gray, (9, 9), 0)
    show_img("gaussian", gaussian)
    # 開運算去除白色噪點
    kernel = np.ones((9, 9), np.uint8)
    open = cv2.morphologyEx(gaussian, cv2.MORPH_OPEN, 
                            kernel, iterations=3)
    show_img("open", open)
    return open
# 計算物體中間點
def midpoint(point1, point2):
    point = ((point1[0]+point2[0])/2, (point1[1]+point2[1])/2)
    return point
def edge_detect(image):
    # 以canny邊緣檢測算法獲取目標(50~100為閾值)
    edged = cv2.Canny(image, 50, 100) #低於50刪除 高於100留下
    show_img("edged", edged)
    # 在邊緣圖像中尋找物體輪廓
    contours1, hierarchy = cv2.findContours(edged.copy(), 
                                            cv2.RETR_EXTERNAL,
                                            cv2.CHAIN_APPROX_SIMPLE)
    # 物體輪廓由左到右進行排序
    (contours2, _) = contours.sort_contours(contours1)
    # 若輪廓面積小於100,視為噪音濾除
    contours2 = [i for i in contours2 if cv2.contourArea(i) > 100]
    # 初始化 pixels per metric
    pixelsPerMetric = None
    return contours2, pixelsPerMetric
# 計算物體像素尺寸,並轉換為實際尺寸
def measure(image, contours2, pixelsPerMetric):
    origin = image.copy()
    for i in contours2:
        # 計算出物品輪廓之外切線框
        box = cv2.minAreaRect(i)
        box = cv2.cv.BoxPoints(box) if imutils.is_cv2() \
              else cv2.boxPoints(box)
        box = np.array(box, dtype="int")
        # 左上角座標開始順時針排序,並畫出外切線框
        box = perspective.order_points(box)
        cv2.drawContours(origin, [box.astype("int")], -1, (0, 255, 0), 2)
        # 畫書外切線框端點
        for (x, y) in box:
            cv2.circle(origin, (int(x), int(y)), 5, (0, 0, 255), -1)
        # 算出左上和右上端點的中心點、左下和右下端點的中心點
        (tl, tr, br, bl) = box
        (tltrX, tltrY) = midpoint(tl, tr)
        (blbrX, blbrY) = midpoint(bl, br)
        # 算出左上和左下端點的中心點、右上和右下端點的中心點
        (tlblX, tlblY) = midpoint(tl, bl)
        (trbrX, trbrY) = midpoint(tr, br)
        # 計算兩個中心點距離(dA:寬、dB:長)
        dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
        dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))
        # 像素值與最左邊物品實際長度比值
        if pixelsPerMetric is None:
            pixelsPerMetric = dB / image_width
        # 計算目標的實際大小
        dimA = dA / pixelsPerMetric
        dimB = dB / pixelsPerMetric
        # 在圖片中標註物體尺寸
        cv2.putText(origin, "{:.1f}cm".format(dimB),
                    (int(tltrX - 15), int(tltrY - 10)), 
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.65, (255, 255, 255), 2)
        cv2.putText(origin, "{:.1f}cm".format(dimA),
                    (int(trbrX - 10), int(trbrY)), 
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.65, (255, 255, 255), 2)
    show_img("Image", origin)
    return origin
if __name__ == "__main__":
    image_path = './edge.jpg'
    image_width = 10.8
    image = cv_imread(image_path)
    show_img("image", image)
    gray = preprocessing(image)
    contours2, pixelsPerMetric = edge_detect(gray)
    measure(image, contours2, pixelsPerMetric)   
1.5 執行結果
讀取edge.jpg原圖
將圖片轉換成灰階
高斯模糊保留圖像主要輪廓,並降低躁點影響。
開運算去除白色噪點(如:滑鼠上白色英文單字)
以Canny邊緣檢測算法獲取待量測物體
計算輪廓長度高度,並畫出外切線框
讓我們繼續看下去...